/*
 * @Date         : 2023-08-06 12:51:42
 * @Description  : 请求类
 */

import { isBoolean, isFunction } from '@/utils/check'
import { merge, encodeParams } from '@/utils/utils'

export default class Request {
  constructor(config = {}) {
    this.config = {
      baseUrl: '', // 请求的根域名
      prevUrl: '', // 上一次请求的url
      prevStatus: '', // 上一次请求的状态 wait-等待中，success-成功，fail-失败
      header: { 'content-type': 'application/json; charset=utf-8' }, // 默认的请求头
      timeout: 10000, // 超时时间
      method: 'POST', // 请求方式
      dataType: 'json', // 设置为json，返回后uni.request会对数据进行一次JSON.parse
      responseType: 'text', // 此参数无需处理，因为5+和支付宝小程序不支持，默认为text即可
      loading: true, // 是否显示请求中的loading
      loadingTime: 1500, // 在此时间内，请求还没回来的话，就显示加载中动画，单位ms
      timer: null, // 定时器
      originalData: false, // 是否在拦截器中返回服务端的原始数据，见文档说明
      loadingMask: true, // 展示loading的时候，是否给一个透明的蒙层，防止触摸穿透
      handleError: true, // 是否处理错误
      requestIntercept: null, // 请求拦截器
      responseIntercept: null, // 响应拦截器
      errorIntercept: null, // 错误拦截器
      completeCallback: () => {} // 请求完成回调
    }

    this.setConfig(config)
  }

  // get请求
  get(url = '', data = {}, options = {}) {
    return this.request({ url, data, method: 'GET', ...options })
  }

  // post请求
  post(url = '', data = {}, options = {}) {
    return this.request({ url, data, method: 'POST', ...options })
  }

  // put请求，不支持支付宝小程序(HX2.6.15)
  put(url = '', data = {}, options = {}) {
    return this.request({ url, data, method: 'PUT', ...options })
  }

  // delete请求，不支持支付宝和头条小程序(HX2.6.15)
  delete(url = '', data = {}, options = {}) {
    return this.request({ url, data, method: 'DELETE', ...options })
  }

  // #ifdef WEB
  // jsonp请求
  jsonp(url = '', data = {}) {
    //参数解析  URL为访问的接口 opt为传播的数据  option 为接受参数的回调函数
    return new Promise((resolve, reject) => {
      url = url + encodeParams(data)
      const script = document.createElement('script')
      script.src = url
      script.type = 'text/javascript'
      window[data.callback] = (res) => {
        delete window[data.callback]
        document.head.removeChild(script)
        //接受成功后调用resolve
        if (res) resolve(res)
        else reject('服务器暂没有获取到数据')
      }
      script.addEventListener('error', () => {
        delete window[data.callback]
        document.head.removeChild(script)
        reject('服务器加载失败！')
      })
      document.head.appendChild(script)
    })
  }
  // #endif

  // 设置全局默认配置
  setConfig(config) {
    this.config = merge(this.config, config)
  }
  // 获取全局配置
  getConfig() {
    return this.config
  }

  // 主要请求部分
  async request(options = {}) {
    // 判断用户传递的URL是否/开头,如果不是,加上/
    options.api =
      options.url.indexOf('/') == 0 ? options.url : '/' + options.url
    options.base = this.config.baseUrl
    options.url = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?/.test(
      options.url
    )
      ? options.url
      : this.config.baseUrl + options.api

    options.isApi = options.url.includes(this.config.baseUrl)
    options.prevUrl = this.config.prevUrl
    options.prevStatus = this.config.prevStatus
    options.data = options.data || {}
    options.method = options.method || this.config.method
    options.timeout = options.timeout || this.config.timeout
    options.loading = isBoolean(options.loading)
      ? options.loading
      : this.config.loading
    options.loadingTime = options.loadingTime || this.config.loadingTime
    options.dataType = options.dataType || this.config.dataType
    options.header = Object.assign({}, this.config.header, options.header)
    options.responseType = options.responseType || this.config.responseType
    options.completeCallback =
      options.completeCallback || this.config.completeCallback
    options.handleError =
      options.handleError === false
        ? options.handleError
        : this.config.handleError
    options.isLoading = false

    this.config.prevUrl = options.url
    this.config.prevStatus = 'wait'

    // 检查请求拦截
    try {
      if (options.isApi && isFunction(this.config.requestIntercept)) {
        options = await this.config.requestIntercept({
          ...this.config,
          ...options
        })
      }
    } catch (error) {
      return Promise.reject(error)
    }

    return new Promise((resolve, reject) => {
      // 接口调用结束的回调函数（调用成功、失败都会执行）
      options.complete = async (response) => {
        // 完成回调
        options.completeCallback(options)
        if (options.isLoading) {
          // uni.$emit(`loading-${uni.$getCache('viewId')}`, false)
        }
        clearTimeout(this.config.timer)
        this.config.timer = null
        // 判断用户对拦截返回数据的要求，如果originalData为true，返回所有的数据(response)到拦截器，否则只返回response.data
        if (this.config.originalData) {
          // 检查响应拦截器
          if (
            this.config.responseIntercept &&
            isFunction(this.config.responseIntercept)
          ) {
            // 获取响应拦截返回信息
            const interceptorResponse = await this.config.responseIntercept(
              response,
              { ...this.config, ...options }
            )
            interceptorResponse
              ? resolve(interceptorResponse)
              : reject(response)
          } else {
            // 如果要求返回原始数据，就算没有拦截器，也返回最原始的数据
            resolve(response)
          }
          this.config.prevStatus = 'success'
        } else {
          // 接口响应成功
          if (response.statusCode == 200) {
            // 检查响应拦截器
            if (options.isApi && isFunction(this.config.responseIntercept)) {
              // 获取响应拦截返回信息
              const interceptorResponse = await this.config.responseIntercept(
                response.data,
                { ...this.config, ...options }
              )
              // 如果响应拦截器返回信息，就进入then回调
              interceptorResponse
                ? resolve(interceptorResponse.data)
                : reject(response.data)
            } else {
              // 没有响应拦截器直接返回
              resolve(response.data)
            }
            this.config.prevStatus = 'success'
          } else {
            // 取消请求
            this.config.prevStatus = 'fail'
            // 检查错误拦截器
            if (isFunction(this.config.errorIntercept)) {
              // 获取错误拦截返回信息
              const interceptorError = await this.config.errorIntercept(
                response,
                { ...this.config, ...options }
              )
              // 如果错误拦截器返回信息，就进入then回调
              interceptorError ? reject(interceptorError) : reject(response)
            } else {
              reject(response)
            }
          }
        }
      }

      // 是否显示loading
      // 加一个是否已有timer定时器的判断，否则有两个同时请求的时候，后者会清除前者的定时器id
      // 而没有清除前者的定时器，导致前者超时，一直显示loading
      // if (options.loading && !this.config.timer) {
      //   this.config.timer = setTimeout(() => {
      //     options.isLoading = true
      //     uni.$emit(`loading-${uni.$getCache('viewId')}`, true)
      //     this.config.timer = null
      //   }, options.loadingTime)
      // }
      const task = uni.request(options)
      options?.task && options.task(task, options)
    })
  }
}
